package cn.yeziji.forum.service.impl.pay;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.yeziji.forum.basic.pay.UnifiedOrderV3Model;
import cn.yeziji.forum.common.ForumResult;
import cn.yeziji.forum.common.enums.PayFilePrefixEnum;
import cn.yeziji.forum.common.enums.status.PayStatus;
import cn.yeziji.forum.dao.intf.MerchantWechatPayInfoDao;
import cn.yeziji.forum.dto.pay.merchant.MerchantWechatPayInfoDTO;
import cn.yeziji.forum.dto.pay.path.WechatPathDTO;
import cn.yeziji.forum.dto.pay.wechat.WechatV3CertificatesDTO;
import cn.yeziji.forum.dubbo.OpenFileService;
import cn.yeziji.forum.exception.PayException;
import cn.yeziji.forum.service.pay.WxPayService;
import cn.yeziji.forum.utils.ForumResultUtils;
import cn.yeziji.forum.utils.NanoIdUtils;
import com.alibaba.dubbo.config.annotation.Reference;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethod;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.enums.WxDomain;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 微信支付服务 实现类
 *
 * @author hwy
 * @since 2023/07/28 23:29
 **/
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {
  @Value("${pay.fileRootPath}")
  private String fileRootPath;
  @Resource
  private MerchantWechatPayInfoDao merchantWechatPayInfoDao;
  @Reference(interfaceClass = OpenFileService.class)
  private OpenFileService openFileService;
  @Resource
  private HttpServletRequest request;
  @Resource
  private HttpServletResponse response;

  @Override
  public String nativePay(Integer userId) {
    WechatV3CertificatesDTO wechatV3Certificates = this.getWechatV3Certificates(userId);
    MerchantWechatPayInfoDTO merchantWechatPayInfo = wechatV3Certificates.getMerchantWechatPayInfo();
    // 测试先用 native
    UnifiedOrderV3Model orderModel = UnifiedOrderV3Model.builder().appid(merchantWechatPayInfo.getAppId())
        .mchid(merchantWechatPayInfo.getMerchantNumber())
        .description("测试 desc")
        .out_trade_no( WxPayKit.generateStr())
        .time_expire(getExpiredTime(5))
        .attach("测试测试 attach")
        .notify_url(merchantWechatPayInfo.getDomain().concat("notify/" + NanoIdUtils.randomNotSymbolNaoId(10)))
        .total("0.01")
        .build();
    log.info("统一下单参数 {}", JSONUtil.toJsonStr(orderModel));
    try {
      WechatPathDTO wechatPathInfo = wechatV3Certificates.getWechatPathInfo();
      IJPayHttpResponse response = WxPayApi.v3(
          RequestMethod.POST,
          WxDomain.CHINA.toString(),
          WxApiType.NATIVE_PAY.toString(),
          merchantWechatPayInfo.getMerchantNumber(),
          this.getSerialNumber(wechatPathInfo.getCertPath()),
          null,
          wechatPathInfo.getKeyPath(),
          JSONUtil.toJsonStr(orderModel)
      );
      log.info("统一下单响应 -> {}", response);
      boolean verifySignature = WxPayKit.verifySignature(response, wechatV3Certificates.getPlatformCertPath());
      if (verifySignature) {
        JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
        log.info("body -> {}", jsonObject);
        return jsonObject.getStr("code_url");
      }
      throw new PayException(PayStatus.PAY_FAILED);
    } catch (Exception e) {
      log.error("发起支付失败 -> {}", response, e);
      throw new PayException(PayStatus.PAY_FAILED);
    }
  }

  @Override
  @SneakyThrows
  public ForumResult payNotify(String notice) {
    log.info("响应回调 -> {}", notice);
    // 测试商户号
    WechatV3CertificatesDTO wechatV3Certificates = this.getWechatV3Certificates(19);
    MerchantWechatPayInfoDTO merchantWechatPayInfo = wechatV3Certificates.getMerchantWechatPayInfo();
    String timestamp = request.getHeader("Wechatpay-Timestamp");
    String nonce = request.getHeader("Wechatpay-Nonce");
    String serialNo = request.getHeader("Wechatpay-Serial");
    String signature = request.getHeader("Wechatpay-Signature");
    log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
    String result = HttpKit.readData(request);
    log.info("支付通知密文 {}", result);
    // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
    String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
        merchantWechatPayInfo.getApiKeyV3(), wechatV3Certificates.getPlatformCertPath());

    log.info("支付通知明文 {}", plainText);

    if (StrUtil.isNotEmpty(plainText)) {
      return ForumResultUtils.ok();
    } else {
      response.setStatus(500);
      return ForumResultUtils.error();
    }
  }

  /**
   * 保存证书
   *
   * @param wechatV3CertificatesDTO 请求证书后响应的标签
   * @param certPath 证书的保存地址
   */
  private void savePlatformCert(WechatV3CertificatesDTO wechatV3CertificatesDTO, String certPath) {
    try {
      String publicKey = "";
      // 将生成的证书写入指定路径，文件名为：cert.pem
      String saveCertPath = certPath + "/platform_cert.pem";
      File file = new File(saveCertPath);
      // 不存在就生成文件进行保存
      if (!file.exists()) {
        AesUtil aesUtil = new AesUtil(wechatV3CertificatesDTO.getApiKeyV3().getBytes(StandardCharsets.UTF_8));
        // 平台证书密文解密
        publicKey = aesUtil.decryptToString(
            wechatV3CertificatesDTO.getAssociatedData().getBytes(StandardCharsets.UTF_8),
            wechatV3CertificatesDTO.getNonce().getBytes(StandardCharsets.UTF_8),
            wechatV3CertificatesDTO.getCiphertext()
        );
        // 保存证书
        log.info("{} 证书不存在, 开始生成保存新的证书: {}", publicKey, saveCertPath);
        FileOutputStream fos = new FileOutputStream(saveCertPath);
        fos.write(publicKey.getBytes());
        fos.flush();
        fos.close();
      } else {
        StringBuilder contentBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(saveCertPath))) {
          String line;
          while ((line = reader.readLine()) != null) {
            contentBuilder.append(line).append("\n");
          }
        }
        publicKey = contentBuilder.toString();
      }
      // 获取平台证书序列号
      X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
      // 赋值回传
      wechatV3CertificatesDTO.setCertificateSerialNumber(certificate.getSerialNumber().toString(16).toUpperCase());
      wechatV3CertificatesDTO.setPlatformCertPath(saveCertPath);
    } catch (Exception e) {
      log.error("生成证书异常 -> {}", e.getMessage(), e);
      throw new PayException(PayStatus.USER_NO_SUPPORT_WECHAT_PAY);
    }
  }

  /**
   * 获取商户的微信 V3 证书
   *
   * @param userId 商户 id
   * @return {@link WechatV3CertificatesDTO} 微信 V3 证书对象
   */
  @SneakyThrows
  private WechatV3CertificatesDTO getWechatV3Certificates(Integer userId) {
    MerchantWechatPayInfoDTO merchantWechatPayInfoDTO = merchantWechatPayInfoDao.getByUserId(userId);
    // 获取微信文件路径信息
    WechatPathDTO wechatPathDTO = (WechatPathDTO) openFileService.getMerchantPayFilePath(userId, PayFilePrefixEnum.WECHAT);
    // 发送获取证书
    IJPayHttpResponse response = WxPayApi.v3(RequestMethod.GET,
        WxDomain.CHINA.toString(),
        WxApiType.GET_CERTIFICATES.toString(),
        merchantWechatPayInfoDTO.getMerchantNumber(),
        getSerialNumber(wechatPathDTO.getCertPath()),
        null,
        wechatPathDTO.getKeyPath(),
        "");
    // 截取响应
    WechatV3CertificatesDTO wechatV3CertificatesDTO = WechatV3CertificatesDTO.buildPayInfo(response, merchantWechatPayInfoDTO, wechatPathDTO);
    log.info("response bean -> {}", wechatV3CertificatesDTO.getSerialNumber());
    // 生成平台证书
    this.savePlatformCert(wechatV3CertificatesDTO, PayFilePrefixEnum.WECHAT.getUserRelativePayPrefixPath(fileRootPath, userId));
    // 验签结果
    boolean verifySignature;
    try {
      verifySignature = WxPayKit.verifySignature(response, wechatV3CertificatesDTO.getPlatformCertPath());
      log.info("{} verify status -> {}", merchantWechatPayInfoDTO.getId(), verifySignature);
      if (verifySignature) {
        return wechatV3CertificatesDTO;
      }
      throw new PayException(PayStatus.USER_NO_SUPPORT_PAY);
    } catch (Exception e) {
      log.error("校验证书异常 -> {}", e.getMessage(), e);
    }
    // 验签
    return wechatV3CertificatesDTO;
  }

  @SneakyThrows
  private String getSerialNumber(String certPath) {
    X509Certificate certificate = PayKit.getCertificate(Files.newInputStream(new File(certPath).toPath()));
    Date notAfter = certificate.getNotAfter();
    if (notAfter.before(new Date())) {
      log.error("{} 证书已过期", certPath);
      throw new PayException(PayStatus.USER_NO_SUPPORT_WECHAT_PAY);
    }
    return certificate.getSerialNumber().toString(16).toUpperCase();
  }

  public String getExpiredTime(long minute) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
    return simpleDateFormat.format(DateUtil.date(System.currentTimeMillis() + 1000L * 60 * minute));
  }

  public static void main(String[] args) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
    String format = simpleDateFormat.format(DateUtil.date(System.currentTimeMillis() + 1000 * 60 * 3));
    System.out.println(format);
  }
}
